/*-*-C-*-
 * objedit source code for Misc CSE
 */

#include "resed.h"
#include "main.h"

#include "swicall.h"
#include "wimp.h"
#include "resformat.h"
#include "newmsgs.h"

#include "dbox.h"

#include "format.h"
#include "relocate.h"
#include "objedit.h"

#include "objdefs.h"
#include "object.h"
#include "icondefs.h"
#include "protocol.h"



/*
 * Make a malloced copy of a string.  If the src string is NULL, return NULL.
 */

char *copystring (char *src)
{
    if (src != NULL)
    {
        char *dst = malloc (strlen (src) + 1);
        if (dst) strcpy (dst, src);
        return dst;
    }
    else
        return NULL;
}


/*
 * If the location addressed by s contains NULL, do nothing. Otherwise, make
 *  a copy of the string addressed by *s, and store its address at *s.
 *
 * Result is NULL unless memory runs out, in which case a suitable error
 *  message is returned.
 */

error * clonestring (char **s)
{
    char *clone;

    if (*s == NULL)
        return NULL;

    clone = (char *) malloc (strlen(*s) + 1);
    if (clone == NULL)
        return error_lookup ("NoMem");

    strcpy (clone, *s);
    *s = clone;
    return NULL;
}


/*
 * Compare two values which may be NULL or may be strings
 */

Bool equalstrings (char *s, char *t)
{
    return (s == NULL && t == NULL ||
            s != NULL && t != NULL && strcmp (s, t) == 0);
}


/*
 * Load an object from the the shell according to the RESED_OBJECT_LOAD
 * message 'load'.
 *
 * If 'object' is NULL, then create a new ObjectRec and load the data into
 *  that.
 *
 * If 'object' is non-NULL, then close and free it first; this option is
 *  used when the Shell forces us to re-load a modified object after
 *  messages are imported.
 *
 * Called from received_resed_object_load(..) in c.protocol when the shell
 *  sends object data to us to load.
 */

error * objedit_load (ObjectPtr object,
                      ResourceFileObjectTemplateHeaderPtr obj,
                      MessageResEdObjectLoadPtr load,
                      ObjectDefPtr def)
{
    ObjectTemplatePtr body = (ObjectTemplatePtr) relocate_object (obj);
    int i;
    Bool display;
    WindowPtr proto = NULL;
    RectRec visarea;
    PointRec scrolloffset;
    int behind;

    if (body == NULL)
        return error_lookup ("CorruptWindow");

    /*
     * If this request to load is a result of a force-load of an object which
     *  we do not already know about, then don't display it - it's a result
     *  of message import, and will be grabbed back and deleted forthwith.
     */

    display = object != NULL || (load->flags & BIT(0)) == 0;

    /*
     * If we already have a copy of this object, we must delete it before
     *  loading the revised version.
     */

    if (object)
    {
        /* note position before closing:
            - in prototype, so that the revised dbox will be opened in the
               same position;
            - locally, so that the prototype values can be replaced */
        swi (Wimp_GetWindowState, R1, object->dbox, END);
        proto = object->def->proto;
        visarea = proto->visarea;
        proto->visarea = object->dbox->visarea;
        scrolloffset = proto->scrolloffset;
        proto->scrolloffset = object->dbox->scrolloffset;
        behind = proto->behind;
        proto->behind = object->dbox->behind;

        /* preserve "offset" value if its an unknown object dbox */
        if (def->templatename == unkobjectdef.templatename)
        {
            char *src = dbox_getstring (object->dbox, I_UNKNOWN_OFFSET);
            IconPtr icons = proto->icons;
            char *dst = (char *) icons[I_UNKNOWN_OFFSET].data[0];

            strcpy (dst, src);
        }

        objedit_close_object (object, FALSE); /* FALSE => don't notify
                                                 the shell */
    }

    /* Allocate and clear a new object structure */
    {
        int size = offsetof (ObjectRec, body) + def->body.size;

        object = (ObjectPtr) calloc (1, size);

        if (object == NULL)
            return error_lookup ("NoMem");
    }

    /* fill in local fields of the ObjectRec */
    object->def = def;
    object->documentID = load->documentID;
    object->objectID = load->objectID;
    for (i=0; i < MAX_OBJECT_NAME; i++)
        object->name[i] = obj->hdr.name[i];

    /* load the object proper */
    if (object_load (object, body) == NULL)
        return error_lookup ("NoMem");

    /* create dbox and display it (if appropriate) */
    object_open_dbox (object, display);

    /* restore prototype's position if necessary */
    if (proto != NULL)
    {
        proto->visarea = visarea;
        proto->scrolloffset = scrolloffset;
        proto->behind = behind;

        /* reset "offset" value if unknown object prototype */
        if (def->templatename == unkobjectdef.templatename)
        {
            IconPtr icons = proto->icons;
            char *dst = (char *) icons[I_UNKNOWN_OFFSET].data[0];

            strcpy (dst, "0");
        }
    }

    return NULL;
}


/*
 * The shell has informed us that this object's name has changed.
 *
 * Record the new name, and update the titlebar of its dialogue box.
 */

error * objedit_rename_object (ObjectPtr object, char *name)
{
    /* record new name for window object */
    sprintf (object->name, "%.*s", sizeof (object->name) - 1, name);

    /* Update title of dbox */
    return object_dbox_update_window_name (object, name);
}


/*
 * Close the object's dbox, and then free all space occupied by the
 *  ObjectRec.
 *
 * If the Bool 'notifyshell' is TRUE, then notify the shell (the data must
 * already have been transferred back).
 *
 * Called from obj_dbox_mouse_click(..) after SEL/CANCEL click, and also
 *  from received_resed_object_loaded(..) after successfully sending an
 *  object back to the shell following a SEL/OK click.
 */

error * objedit_close_object (ObjectPtr object, Bool notifyshell)
{
    if (notifyshell)
    {
/*        dprintf ("MISC: notifying shell\n"); */
        ER ( protocol_send_resed_object_closed (object) );
    }

    /* close, delete and free everything to do with the ObjectRec */
    object_free (object);

    return NULL;
}


/*
 * Return the size in bytes that the specified object will occupy when
 * saved as a ResourceFileObjectTemplateHeaderPtr for transfer to
 * the shell.
 *
 * Called from protocol_send_resed_object_sending(..) to determine how much
 *  space to allocate for the object template to be sent back to the shell.
 */

int objedit_object_size (
    ObjectPtr object,
    int *bodysize, int *stringsize, int *msgsize, int *numrelocs
)
{
    /* determine space required for the object template and its tables */
    object_size (object, bodysize, stringsize, msgsize, numrelocs);

    /* round up to word boundaries */
    *bodysize = (*bodysize + 3) & ~ 3;
    *stringsize = (*stringsize + 3) & ~ 3;
    *msgsize = (*msgsize + 3) & ~ 3;

    return sizeof (ResourceFileObjectTemplateHeaderRec) +
           *bodysize + *stringsize + *msgsize +
           sizeof (int) + *numrelocs * sizeof (RelocationRec);
}


/*
 * Save the specified object to a pre-allocated block of memory as a
 * ResourceFileObjectTemplateHeaderRec ready for transfer to the
 * shell.  The block is expected to be big enough as measured
 * with objedit_object_size().
 *
 * A note on relocations.  In order to make the "export/import messages"
 * facility as robust as possible, we always output a relocation record
 * for every potential message in the object, even those that are NULL.
 * NULL strings are represented by a relocation record that points to a
 * -1 word (the toolbox loader turns this into a NULL when it is relocated).
 * This ensures that the numbering won't be changed between message export
 * and import if changes have been made to the object's messages since the
 * messages were exported.
 */

error * objedit_save_object_to_memory (ObjectPtr object, char *buffer,
                    int bodysize, int stringsize, int msgsize, int numrelocs)
{
    ResourceFileObjectTemplateHeaderPtr obj =
                                (ResourceFileObjectTemplateHeaderPtr) buffer;
    ObjectTemplatePtr body = (ObjectTemplatePtr)
                     (buffer + sizeof (ResourceFileObjectTemplateHeaderRec));
    int offset = sizeof (ResourceFileObjectTemplateHeaderRec) + bodysize;
    TemplateInfoRec tip;
    RelocationTablePtr reloctable;
    char *next;

    /* fill in table offsets, and construct Template Information Record
       for calls to relocate_make_ref(..) */
    tip.body = (char *) body;
    obj->stringtableoffset = stringsize ? offset : -1;
    tip.string = tip.stringbase = buffer + offset;
    offset += stringsize;
    obj->messagetableoffset = msgsize ? offset : -1;
    tip.msg = tip.msgbase = buffer + offset;
    offset += msgsize;
    obj->relocationtableoffset = offset;
    reloctable = (RelocationTablePtr) (buffer + offset);
    reloctable->numrelocations = numrelocs;
    tip.reloc = reloctable->relocations;
    tip.numrelocs = 0;

    /* construct ObjectTemplateHeaderRec */
    obj->hdr.class = object->def->class;
    /* obj->hdr.flags filled in by shell */
    obj->hdr.version = object->def->version;
    /* obj->hdr.name filled in by shell */
    obj->hdr.totalsize = sizeof (ObjectTemplateHeaderRec) +
                                   bodysize + stringsize + msgsize;
    obj->hdr.bodyoffset = (Offset) sizeof (ObjectTemplateHeaderRec);
    obj->hdr.bodysize = bodysize;

    /* reassemble the object template itself */
    next = object_save (&tip, object, body);

    /* pad out strings and message tables with zeros */
    while ((int) tip.string & 3)
    {
        *tip.string = 0;
        tip.string++;
    }
    while ((int) tip.msg & 3)
    {
        *tip.msg = 0;
        tip.msg++;
    }

    /* consistency checks! */
    if (next != tip.stringbase)
        error_exit (error_lookup ("SaveChk1",
                                  (int) next, (int) tip.stringbase));
    if (tip.string != tip.msgbase)
        error_exit (error_lookup ("SaveChk2",
                                  (int) tip.string, (int) tip.msgbase));
    if (tip.msg != (char *) reloctable)
        error_exit (error_lookup ("SaveChk3",
                                  (int) tip.msg, (int) reloctable));
    if (tip.numrelocs != tip.numrelocs)
        error_exit (error_lookup ("SaveChk4", tip.numrelocs, numrelocs));
    
    return NULL;
}
